#include <iostream>
#include <list>
#include <algorithm>
#include <string>
#include <ctime>
#include <cstdlib>
#include <cmath>
#include <gl/glut.h>
#include "geom.h"
#include "glex.h"
#include "object.h"
#include "utility.h"

using namespace std;

const char *skyboxTexturePath[] = { "images/skybox_front.png",
		"images/skybox_back.png", "images/skybox_left.png",
		"images/skybox_right.png", "images/skybox_top.png",
		"images/skybox_bottom.png", "", };

Rectangle window;
Camera *camera;
AirPlane *plane;
DrawList drawList;
DrawList *preList, *balloonList, *bulletList, *trailList, *postList;
TextureList skyboxTex(6);
Point2f mousePos;
bool mouseDown = false;
bool shooting = false;
float focalLength = 24 * mm;
PlaneControl *planeCtrl;
void render() {
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	camera->updateCamera();
	glLightfv(GL_LIGHT0, GL_POSITION, camera->position.getArray());
	drawList.draw();
	int err = glGetError();
	if (err != 0) {
		cerr << gluErrorString(err) << endl << flush;
	}
	glutSwapBuffers();
}

void reshape(int w, int h) {
	window.reset(w, h);
	camera->setupPerspective(w, h);
}

void mouseMove(int x, int y) {
	mousePos = window.center() - Point2f(x, y);

}
void doShoot(int t);
void mouseEvent(int button, int state, int x, int y) {
	if (button == GLUT_RIGHT_BUTTON) {
		if (state == GLUT_DOWN) {
			focalLength = 100 * mm;
		} else {
			focalLength = 24 * mm;
		}
	} else {
		if (state == GLUT_DOWN) {
			mouseDown = true;
			if (!shooting) {
				glutTimerFunc(0, doShoot, 200);
			}
			shooting = true;
		} else {
			mouseDown = false;
			shooting = false;
		}
	}
}

void doShoot(int t) {
	static const float thresholdAngle = 3 * DEG;
	if (mouseDown && shooting) {
		list<Drawable*> balloons = balloonList->getList();
		typedef list<Drawable*>::iterator iter;
		Balloon *target = NULL;
		float minTh = 100000;
		for (iter it = balloons.begin(); it != balloons.end(); it++) {
			Balloon* bal = dynamic_cast<Balloon*>(*it);
			Vector4f direction = bal->position - camera->position;
			float distance = direction.length() / 100.0f;
			if (distance < 1) {
				distance = 1;
			}
			direction.normalize();
			float th = acos(camera->frontVector() ^ direction);
			if (th < thresholdAngle / distance && th < minTh) {
				target = bal;
				minTh = th;
			}
		}
		*bulletList << new Bullet(camera, planeCtrl, target);
		glutTimerFunc(t, doShoot, t);
	}
}

void idleFunc() {
	static clock_t oldTime = clock();
	clock_t newTime = clock();
	float dt = (float) (newTime - oldTime) / (float) CLOCKS_PER_SEC;
	oldTime = newTime;
	if (dt == 0) {
		dt = 0.00001;
	}
	float sx = 2.0f * 60.0f / window.width;
	float sy = 2.0f * 45.0f / window.height;
	float dx = mousePos.x * sx * DEG;
	float dy = mousePos.y * sy * DEG;
	plane->elevator(-dy);
	plane->aileron(dx);
	plane->rudder(dx);
	plane->update(dt);
	drawList.update(dt);
	while (Balloon::getCount() < 500) {
		*balloonList << Balloon::genBalloon(camera);
	}
	if (abs(camera->lens.focalLength - focalLength) > 0.001) {
		camera->lens.focalLength += (focalLength - camera->lens.focalLength) * 4
				* dt;
		camera->setupPerspective(window.width, window.height);
	}
	glutPostRedisplay();
}

void initGL() {
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	window.reset(800, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("Balloon Hunter");
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glutReshapeFunc(reshape);
	glutMouseFunc(mouseEvent);
	glutMotionFunc(mouseMove);
	glutPassiveMotionFunc(mouseMove);
	glutDisplayFunc(render);
	glutIdleFunc(idleFunc);
}

void initCamera() {
	camera = new Camera();
	camera->film = Film::FILM_135;
	camera->lens = Lens(focalLength);
}

void initDrawList() {
	preList = new DrawList();
	balloonList = new DrawList();
	bulletList = new DrawList();
	postList = new DrawList();
	trailList = new DrawList();
	drawList << preList << balloonList << bulletList << trailList << postList;
	skyboxTex.load(skyboxTexturePath);
	*preList << new Skybox(camera, skyboxTex, 6);
	for (int i = 0; i < 500; i++) {
		*balloonList << Balloon::genBalloon(camera);
	}
	planeCtrl = new PlaneControl(camera, plane);
	planeCtrl->deadTime = clock() + 60 * CLOCKS_PER_SEC;
	*postList << planeCtrl;

	Bullet::trailList = trailList;
}

void init(void) {
	srand((unsigned int) time(NULL));
	initGL();
	initCamera();
	plane = new AirPlane(camera);
	initDrawList();
}

void release(void) {
	delete camera;
	delete plane;
}

int main(int argc, char** argv) {
	glutInit(&argc, argv);
	init();
	glutMainLoop();
	release();
	return (0);
}
